using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;


namespace Ultima {
  /// <summary>
  /// Contains translation tables used for mapping body values to file subsets.
  /// <seealso cref="Animations" />
  /// </summary>
  public class BodyConverter {
    private static readonly int[] m_Table1;
    private static readonly int[] m_Table2;

    static BodyConverter() {
      string path = Client.GetFilePath( "bodyconv.def" );

      if ( path == null )
        return;

      List<int> list1 = new List<int>(), list2 = new List<int>();
      int max1 = 0, max2 = 0;

      using ( var ip = new StreamReader( path ) ) {
        string line;

        while ( ( line = ip.ReadLine() ) != null ) {
          if ( ( line = line.Trim() ).Length == 0 || line.StartsWith( "#" ) )
            continue;

          try {
            string[] split = line.Split( '\t' );

            int original = System.Convert.ToInt32( split[0] );
            int anim2 = System.Convert.ToInt32( split[1] );
            int anim3;

            try {
              anim3 = System.Convert.ToInt32( split[2] );
            } catch {
              anim3 = -1;
            }

            if ( anim2 != -1 ) {
              if ( anim2 == 68 )
                anim2 = 122;

              if ( original > max1 )
                max1 = original;

              list1.Add( original );
              list1.Add( anim2 );
            }

            if ( anim3 != -1 ) {
              if ( original > max2 )
                max2 = original;

              list2.Add( original );
              list2.Add( anim3 );
            }
          } catch {
          }
        }
      }

      m_Table1 = new int[max1 + 1];

      for ( int i = 0; i < m_Table1.Length; ++i )
        m_Table1[i] = -1;

      for ( int i = 0; i < list1.Count; i += 2 )
        m_Table1[list1[i]] = list1[i + 1];

      m_Table2 = new int[max2 + 1];

      for ( int i = 0; i < m_Table2.Length; ++i )
        m_Table2[i] = -1;

      for ( int i = 0; i < list2.Count; i += 2 )
        m_Table2[list2[i]] = list2[i + 1];
    }

    private BodyConverter() {
    }

    /// <summary>
    /// Checks to see if <paramref name="body" /> is contained within the mapping table.
    /// </summary>
    /// <returns>True if it is, false if not.</returns>
    public static bool Contains( int body ) {
      if ( m_Table1 != null && body >= 0 && body < m_Table1.Length && m_Table1[body] != -1 )
        return true;

      if ( m_Table2 != null && body >= 0 && body < m_Table2.Length && m_Table2[body] != -1 )
        return true;

      return false;
    }

    /// <summary>
    /// Attempts to convert <paramref name="body" /> to a body index relative to a file subset, specified by the return value.
    /// </summary>
    /// <returns>A value indicating a file subset:
    /// <list type="table">
    /// <listheader>
    /// <term>Return Value</term>
    /// <description>File Subset</description>
    /// </listheader>
    /// <item>
    /// <term>1</term>
    /// <description>Anim.mul, Anim.idx (Standard)</description>
    /// </item>
    /// <item>
    /// <term>2</term>
    /// <description>Anim2.mul, Anim2.idx (LBR)</description>
    /// </item>
    /// <item>
    /// <term>3</term>
    /// <description>Anim3.mul, Anim3.idx (AOS)</description>
    /// </item>
    /// </list>
    /// </returns>
    public static int Convert( ref int body ) {
      if ( m_Table1 != null && body >= 0 && body < m_Table1.Length ) {
        int val = m_Table1[body];

        if ( val != -1 ) {
          body = val;
          return 2;
        }
      }

      if ( m_Table2 != null && body >= 0 && body < m_Table2.Length ) {
        int val = m_Table2[body];

        if ( val != -1 ) {
          body = val;
          return 3;
        }
      }

      return 1;
    }
  }


  public class Animations {
    private static readonly FileIndex m_FileIndex = new FileIndex( "Anim.idx", "Anim.mul", 0x40000, 6 );

    private static readonly FileIndex m_FileIndex2 = new FileIndex( "Anim2.idx", "Anim2.mul", 0x10000, -1 );

    private static readonly FileIndex m_FileIndex3 = new FileIndex( "Anim3.idx", "Anim3.mul", 0x20000, -1 );
    private static int[] m_Table;

    private Animations() {
    }

    public static FileIndex FileIndex {
      get { return m_FileIndex; }
    }
    public static FileIndex FileIndex2 {
      get { return m_FileIndex; }
    }
    public static FileIndex FileIndex3 {
      get { return m_FileIndex; }
    }

    public static Frame[] GetAnimation( int body, int action, int direction, int hue, bool preserveHue ) {
      if ( preserveHue )
        Translate( ref body );
      else
        Translate( ref body, ref hue );

      int fileType = BodyConverter.Convert( ref body );
      FileIndex fileIndex;

      int index;

      switch ( fileType ) {
        default:
        case 1: {
            fileIndex = m_FileIndex;

            if ( body < 200 )
              index = body * 110;
            else if ( body < 400 )
              index = 22000 + ( ( body - 200 ) * 65 );
            else
              index = 35000 + ( ( body - 400 ) * 175 );

            break;
          }
        case 2: {
            fileIndex = m_FileIndex2;

            if ( body < 200 )
              index = body * 110;
            else
              index = 22000 + ( ( body - 200 ) * 65 );

            break;
          }
        case 3: {
            fileIndex = m_FileIndex3;

            if ( body < 300 )
              index = body * 65;
            else if ( body < 400 )
              index = 33000 + ( ( body - 300 ) * 110 );
            else
              index = 35000 + ( ( body - 400 ) * 175 );

            break;
          }
      }

      index += action * 5;

      if ( direction <= 4 )
        index += direction;
      else
        index += direction - ( direction - 4 ) * 2;

      int length, extra;
      bool patched;
      Stream stream = fileIndex.Seek( index, out length, out extra, out patched );

      if ( stream == null )
        return null;

      bool flip = ( direction > 4 );

      var bin = new BinaryReader( stream );

      var palette = new ushort[0x100];

      for ( int i = 0; i < 0x100; ++i )
        palette[i] = (ushort)( bin.ReadUInt16() ^ 0x8000 );

      var start = (int)bin.BaseStream.Position;
      int frameCount = bin.ReadInt32();

      var lookups = new int[frameCount];

      for ( int i = 0; i < frameCount; ++i )
        lookups[i] = start + bin.ReadInt32();

      bool onlyHueGrayPixels = ( ( hue & 0x8000 ) == 0 );

      hue = ( hue & 0x3FFF ) - 1;

      Hue hueObject = null;

      if ( hue >= 0 && hue < Hues.List.Length )
        hueObject = Hues.List[hue];

      var frames = new Frame[frameCount];

      for ( int i = 0; i < frameCount; ++i ) {
        bin.BaseStream.Seek( lookups[i], SeekOrigin.Begin );
        frames[i] = new Frame( palette, bin, flip );

        if ( hueObject != null )
          hueObject.ApplyTo( frames[i].Bitmap, onlyHueGrayPixels );
      }

      return frames;
    }

    public static void Translate( ref int body ) {
      if ( m_Table == null )
        LoadTable();

      if ( body <= 0 || body >= m_Table.Length ) {
        body = 0;
        return;
      }

      body = ( m_Table[body] & 0x7FFF );
    }

    public static void Translate( ref int body, ref int hue ) {
      if ( m_Table == null )
        LoadTable();

      if ( body <= 0 || body >= m_Table.Length ) {
        body = 0;
        return;
      }

      int table = m_Table[body];

      if ( ( table & ( 1 << 31 ) ) != 0 ) {
        body = table & 0x7FFF;

        int vhue = ( hue & 0x3FFF ) - 1;

        if ( vhue < 0 || vhue >= Hues.List.Length )
          hue = ( table >> 15 ) & 0xFFFF;
      }
    }

    private static void LoadTable() {
      int count = 400 + ( ( m_FileIndex.Index.Length - 35000 ) / 175 );

      m_Table = new int[count];

      for ( int i = 0; i < count; ++i ) {
        object o = BodyTable.m_Entries[i];

        if ( o == null || BodyConverter.Contains( i ) )
          m_Table[i] = i;
        else {
          var bte = (BodyTableEntry)o;

          m_Table[i] = bte.m_OldID | ( 1 << 31 ) | ( ( ( bte.m_NewHue ^ 0x8000 ) & 0xFFFF ) << 15 );
        }
      }
    }
  }


  public class Frame {
    private const int DoubleXor = ( 0x200 << 22 ) | ( 0x200 << 12 );

    public static readonly Frame Empty = new Frame();
    public static readonly Frame[] EmptyFrames = new Frame[1] { Empty };
    private readonly Bitmap m_Bitmap;
    private readonly Point m_Center;

    private Frame() {
      this.m_Bitmap = new Bitmap( 1, 1 );
    }

    public unsafe Frame( ushort[] palette, BinaryReader bin, bool flip ) {
      int xCenter = bin.ReadInt16();
      int yCenter = bin.ReadInt16();

      int width = bin.ReadUInt16();
      int height = bin.ReadUInt16();

      var bmp = new Bitmap( width, height, PixelFormat.Format16bppArgb1555 );
      BitmapData bd = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.WriteOnly,
                                   PixelFormat.Format16bppArgb1555 );

      var line = (ushort*)bd.Scan0;
      int delta = bd.Stride >> 1;

      int header;

      int xBase = xCenter - 0x200;
      int yBase = ( yCenter + height ) - 0x200;

      if ( !flip ) {
        line += xBase;
        line += ( yBase * delta );

        while ( ( header = bin.ReadInt32() ) != 0x7FFF7FFF ) {
          header ^= DoubleXor;

          ushort* cur = line + ( ( ( ( header >> 12 ) & 0x3FF ) * delta ) + ( ( header >> 22 ) & 0x3FF ) );
          ushort* end = cur + ( header & 0xFFF );

          while ( cur < end )
            *cur++ = palette[bin.ReadByte()];
        }
      } else {
        line -= xBase - width + 1;
        line += ( yBase * delta );

        while ( ( header = bin.ReadInt32() ) != 0x7FFF7FFF ) {
          header ^= DoubleXor;

          ushort* cur = line + ( ( ( ( header >> 12 ) & 0x3FF ) * delta ) - ( ( header >> 22 ) & 0x3FF ) );
          ushort* end = cur - ( header & 0xFFF );

          while ( cur > end )
            *cur-- = palette[bin.ReadByte()];
        }

        xCenter = width - xCenter;
      }

      bmp.UnlockBits( bd );

      this.m_Center = new Point( xCenter, yCenter );
      this.m_Bitmap = bmp;
    }

    public Point Center {
      get { return this.m_Center; }
    }
    public Bitmap Bitmap {
      get { return this.m_Bitmap; }
    }
  }


  public class BodyTableEntry {
    public int m_NewHue;
    public int m_NewID;
    public int m_OldID;

    public BodyTableEntry( int oldID, int newID, int newHue ) {
      this.m_OldID = oldID;
      this.m_NewID = newID;
      this.m_NewHue = newHue;
    }
  }


  public class BodyTable {
    public static Dictionary<int, BodyTableEntry> m_Entries;

    static BodyTable() {
      m_Entries = new Dictionary<int, BodyTableEntry>();

      string filePath = Client.GetFilePath( "body.def" );

      if ( filePath == null )
        return;

      var def = new StreamReader( filePath );

      string line;

      while ( ( line = def.ReadLine() ) != null ) {
        if ( ( line = line.Trim() ).Length == 0 || line.StartsWith( "#" ) )
          continue;

        try {
          int index1 = line.IndexOf( " {" );
          int index2 = line.IndexOf( "} " );

          string param1 = line.Substring( 0, index1 );
          string param2 = line.Substring( index1 + 2, index2 - index1 - 2 );
          string param3 = line.Substring( index2 + 2 );

          int indexOf = param2.IndexOf( ',' );

          if ( indexOf > -1 )
            param2 = param2.Substring( 0, indexOf ).Trim();

          int iParam1 = Convert.ToInt32( param1 );
          int iParam2 = Convert.ToInt32( param2 );
          int iParam3 = Convert.ToInt32( param3 );

          m_Entries[iParam1] = new BodyTableEntry( iParam2, iParam1, iParam3 );
        } catch {
        }
      }
    }
  }
}